// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
#define NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "crypto/rsa_private_key.h"
#include "net/base/address_list.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
#include "net/cert/x509_certificate.h"
#include "net/socket/ssl_server_socket.h"
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
#include "net/ssl/ssl_server_config.h"
#include "url/gurl.h"

namespace net {

class StreamSocket;
class TCPServerSocket;

namespace test_server {

    class EmbeddedTestServerConnectionListener;
    class HttpConnection;
    class HttpResponse;
    struct HttpRequest;

    // Class providing an HTTP server for testing purpose. This is a basic server
    // providing only an essential subset of HTTP/1.1 protocol. Especially,
    // it assumes that the request syntax is correct. It *does not* support
    // a Chunked Transfer Encoding.
    //
    // The common use case for unit tests is below:
    //
    // void SetUp() {
    //   test_server_.reset(new EmbeddedTestServer());
    //   ASSERT_TRUE(test_server_.Start());
    //   test_server_->RegisterRequestHandler(
    //       base::Bind(&FooTest::HandleRequest, base::Unretained(this)));
    // }
    //
    // std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
    //   GURL absolute_url = test_server_->GetURL(request.relative_url);
    //   if (absolute_url.path() != "/test")
    //     return std::unique_ptr<HttpResponse>();
    //
    //   std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
    //   http_response->set_code(test_server::SUCCESS);
    //   http_response->set_content("hello");
    //   http_response->set_content_type("text/plain");
    //   return http_response;
    // }
    //
    // For a test that spawns another process such as browser_tests, it is
    // suggested to call Start in SetUpOnMainThread after the process is spawned.
    //  If you have to do it before the process spawns, you need to first setup the
    // listen socket so that there is no no other threads running while spawning
    // the process. To do so, please follow the following example:
    //
    // void SetUp() {
    //   ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
    //   ...
    //   InProcessBrowserTest::SetUp();
    // }
    //
    // void SetUpOnMainThread() {
    //   // Starts the accept IO thread.
    //   embedded_test_server()->StartAcceptingConnections();
    // }
    //
    class EmbeddedTestServer {
    public:
        enum Type {
            TYPE_HTTP,
            TYPE_HTTPS,
        };

        enum ServerCertificate {
            CERT_OK,

            CERT_MISMATCHED_NAME,
            CERT_EXPIRED,

            // A certificate with invalid notBefore and notAfter times. Windows'
            // certificate library will not parse this certificate.
            CERT_BAD_VALIDITY,

            // Cross-signed certificate to test PKIX path building. Contains an
            // intermediate cross-signed by an unknown root, while the client (via
            // TestRootStore) is expected to have a self-signed version of the
            // intermediate.
            CERT_CHAIN_WRONG_ROOT,

            // Causes the testserver to use a hostname that is a domain
            // instead of an IP.
            CERT_COMMON_NAME_IS_DOMAIN,
        };

        typedef base::Callback<std::unique_ptr<HttpResponse>(
            const HttpRequest& request)>
            HandleRequestCallback;
        typedef base::Callback<void(const HttpRequest& request)>
            MonitorRequestCallback;

        // Creates a http test server. Start() must be called to start the server.
        // |type| indicates the protocol type of the server (HTTP/HTTPS).
        EmbeddedTestServer();
        explicit EmbeddedTestServer(Type type);
        ~EmbeddedTestServer();

        // Sets a connection listener, that would be notified when various connection
        // events happen. May only be called before the server is started. Caller
        // maintains ownership of the listener.
        void SetConnectionListener(EmbeddedTestServerConnectionListener* listener);

        // Initializes and waits until the server is ready to accept requests.
        // This is the equivalent of calling InitializeAndListen() followed by
        // StartAcceptingConnections().
        // Returns whether a listening socket has been successfully created.
        bool Start() WARN_UNUSED_RESULT;

        // Starts listening for incoming connections but will not yet accept them.
        // Returns whether a listening socket has been succesfully created.
        bool InitializeAndListen() WARN_UNUSED_RESULT;

        // Starts the Accept IO Thread and begins accepting connections.
        void StartAcceptingConnections();

        // Shuts down the http server and waits until the shutdown is complete.
        bool ShutdownAndWaitUntilComplete() WARN_UNUSED_RESULT;

        // Checks if the server has started listening for incoming connections.
        bool Started() const
        {
            return listen_socket_.get() != NULL;
        }

        HostPortPair host_port_pair() const
        {
            return HostPortPair::FromURL(base_url_);
        }

        // Returns the base URL to the server, which looks like
        // http://127.0.0.1:<port>/, where <port> is the actual port number used by
        // the server.
        const GURL& base_url() const { return base_url_; }

        // Returns a URL to the server based on the given relative URL, which
        // should start with '/'. For example: GetURL("/path?query=foo") =>
        // http://127.0.0.1:<port>/path?query=foo.
        GURL GetURL(const std::string& relative_url) const;

        // Similar to the above method with the difference that it uses the supplied
        // |hostname| for the URL instead of 127.0.0.1. The hostname should be
        // resolved to 127.0.0.1.
        GURL GetURL(const std::string& hostname,
            const std::string& relative_url) const;

        // Returns the address list needed to connect to the server.
        bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT;

        // Returns the port number used by the server.
        uint16_t port() const { return port_; }

        void SetSSLConfig(ServerCertificate cert, const SSLServerConfig& ssl_config);
        void SetSSLConfig(ServerCertificate cert);

        // Returns the file name of the certificate the server is using. The test
        // certificates can be found in net/data/ssl/certificates/.
        std::string GetCertificateName() const;

        // Returns the certificate that the server is using.
        scoped_refptr<X509Certificate> GetCertificate() const;

        // Registers request handler which serves files from |directory|.
        // For instance, a request to "/foo.html" is served by "foo.html" under
        // |directory|. Files under sub directories are also handled in the same way
        // (i.e. "/foo/bar.html" is served by "foo/bar.html" under |directory|).
        // TODO(svaldez): Merge ServeFilesFromDirectory and
        // ServeFilesFromSourceDirectory.
        void ServeFilesFromDirectory(const base::FilePath& directory);

        // Serves files relative to DIR_SOURCE_ROOT.
        void ServeFilesFromSourceDirectory(const std::string& relative);
        void ServeFilesFromSourceDirectory(const base::FilePath& relative);

        // Registers the default handlers and serve additional files from the
        // |directory| directory, relative to DIR_SOURCE_ROOT.
        void AddDefaultHandlers(const base::FilePath& directory);

        // The most general purpose method. Any request processing can be added using
        // this method. Takes ownership of the object. The |callback| is called
        // on UI thread.
        void RegisterRequestHandler(const HandleRequestCallback& callback);

        // Adds request monitors. The |callback| is called before any handlers are
        // called, but can not respond it. This is useful to monitor requests that
        // will be handled by other request handlers.
        void RegisterRequestMonitor(const MonitorRequestCallback& callback);

        // Adds default handlers, including those added by AddDefaultHandlers, to be
        // tried after all other user-specified handlers have been tried.
        void RegisterDefaultHandler(const HandleRequestCallback& callback);

        bool FlushAllSocketsAndConnectionsOnUIThread();
        void FlushAllSocketsAndConnections();

    private:
        // Shuts down the server.
        void ShutdownOnIOThread();

        // Upgrade the TCP connection to one over SSL.
        std::unique_ptr<StreamSocket> DoSSLUpgrade(
            std::unique_ptr<StreamSocket> connection);
        // Handles async callback when the SSL handshake has been completed.
        void OnHandshakeDone(HttpConnection* connection, int rv);

        // Begins accepting new client connections.
        void DoAcceptLoop();
        // Handles async callback when there is a new client socket. |rv| is the
        // return value of the socket Accept.
        void OnAcceptCompleted(int rv);
        // Adds the new |socket| to the list of clients and begins the reading
        // data.
        void HandleAcceptResult(std::unique_ptr<StreamSocket> socket);

        // Attempts to read data from the |connection|'s socket.
        void ReadData(HttpConnection* connection);
        // Handles async callback when new data has been read from the |connection|.
        void OnReadCompleted(HttpConnection* connection, int rv);
        // Parses the data read from the |connection| and returns true if the entire
        // request has been received.
        bool HandleReadResult(HttpConnection* connection, int rv);

        // Closes and removes the connection upon error or completion.
        void DidClose(HttpConnection* connection);

        // Handles a request when it is parsed. It passes the request to registered
        // request handlers and sends a http response.
        void HandleRequest(HttpConnection* connection,
            std::unique_ptr<HttpRequest> request);

        // Initializes the SSLServerContext so that SSLServerSocket connections may
        // share the same cache
        void InitializeSSLServerContext();

        HttpConnection* FindConnection(StreamSocket* socket);

        // Posts a task to the |io_thread_| and waits for a reply.
        bool PostTaskToIOThreadAndWait(
            const base::Closure& closure) WARN_UNUSED_RESULT;

        const bool is_using_ssl_;

        std::unique_ptr<base::Thread> io_thread_;

        std::unique_ptr<TCPServerSocket> listen_socket_;
        std::unique_ptr<StreamSocket> accepted_socket_;

        EmbeddedTestServerConnectionListener* connection_listener_;
        uint16_t port_;
        GURL base_url_;
        IPEndPoint local_endpoint_;

        // Owns the HttpConnection objects.
        std::map<StreamSocket*, HttpConnection*> connections_;

        // Vector of registered and default request handlers and monitors.
        std::vector<HandleRequestCallback> request_handlers_;
        std::vector<MonitorRequestCallback> request_monitors_;
        std::vector<HandleRequestCallback> default_request_handlers_;

        base::ThreadChecker thread_checker_;

        net::SSLServerConfig ssl_config_;
        ServerCertificate cert_;
        std::unique_ptr<SSLServerContext> context_;

        base::WeakPtrFactory<EmbeddedTestServer> weak_factory_;

        DISALLOW_COPY_AND_ASSIGN(EmbeddedTestServer);
    };

} // namespace test_server

// TODO(svaldez): Refactor EmbeddedTestServer to be in the net namespace.
using test_server::EmbeddedTestServer;

} // namespace net

#endif // NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
